#define THIS_INCLUDES_THE_MAIN_FUNCTION
#define THIS_IS_STACK_APPLICATION

#include "HardwareProfile.h"
#include "TCPIP_Stack/TCPIP.h"
#include "TCPIP_Stack/Delay.h"
#include "TCPIP_Stack/LCDBlocking.h"
#include "TCPIP_Stack/UDP.h"

// DHCP packet max size is 576 bytes.
#define MAX_DATAGRAM_SIZE 576
// Our maximum data memory is 3808 bytes and banks are only 256 bytes.
// A custom linker script is used to join a few banks.

#define ARP_TIMEOUT 20


#ifdef DEBUG
#undef DEBUG
#endif
/*
#ifndef DEBUG
#define DEBUG
#endif
*/
typedef enum {LISTEN_C, RX_C, ARP_GW, ARP_RX, OPEN_S, TX_S, LISTEN_S, RX_S, OPEN_C, TX_C} state;

APP_CONFIG AppConfig;
NODE_INFO dhcpServer;
NODE_INFO broadcast;
NODE_INFO client;
IP_ADDR gw;

// Buffer for Client <> Server communication.
BYTE buffer[MAX_DATAGRAM_SIZE];

//##########################
//# Start of configuration #
//##########################

const BYTE myIP[] = 		{10, 0, 1, 40};
const BYTE myNM[] = 		{255, 255, 255, 0};
const BYTE gwIP[] = 		{10, 0, 1, 1};
const BYTE DNS1IP[] = 		{10, 0, 1, 1};
const BYTE DNS2IP[] = 		{10, 0, 1, 1};

const BYTE gwPubIP[] =		{10, 0, 0, 40};

const BYTE dhcpServerIP[] = 	{10, 0, 0, 50};

//########################
//# End of configuration #
//########################

static void init();
void relay();
static void InitAppConfig();
void DisplayString(BYTE pos, char* text);
void DisplayIPValue(DWORD IPdw);
size_t strlcpy(char *dst, const char *src, size_t siz);

void LowISR(void) __interrupt (2)
{
	TickUpdate();
}

void HighISR(void) __interrupt(1)
{
}

int main()
{
	init();
	
#ifdef DEBUG
	DisplayString(0,"RXC TXS RXS TXC ");
#endif
	DisplayIPValue(AppConfig.MyIPAddr.Val);

	while(1)
	{
		StackTask();
		
		relay();
	}
}

void relay()
{
	int i;
	int c;
	static int timeoutARP = ARP_TIMEOUT;

	static UDP_SOCKET rx;
	static UDP_SOCKET tx;

	// Pointers to the buffers, needed to receive/send packets
	static BYTE* pRx = &buffer[0];
	static BYTE* pTx = &buffer[0];

	// Offset of the Relay IP Address in the DHCP packet is byte 24..27.
	WORD offsetClientIP = 12;
	WORD offsetYourIP = 16;
	WORD offsetServerIP = 20;
	WORD offsetRelayIP = 24;

	static state s = LISTEN_C;
	
	switch(s)
	{
		case LISTEN_C:
			pRx = &buffer[0];
			pTx = &buffer[0];
			rx = UDPOpen((UDP_PORT)67, NULL, 68);
			
			if(rx == INVALID_UDP_SOCKET)
			{
#ifdef DEBUG
				DisplayString(0,"RXCiTXS RXS TXC ");
#endif
			}
			else
			{
#ifdef DEBUG
				DisplayString(0,"RXCvTXS RXS TXC ");
#endif
				s = RX_C;
			}
			break;
		case RX_C:
			if(UDPIsGetReady(rx))
			{
				while(UDPGet(pRx) == TRUE)
					pRx++;
				
				UDPDiscard();
				
				UDPClose(rx);
				
				s = ARP_RX;
			}
			break;
		case ARP_GW:
			ARPResolve(&gw);
			s = ARP_RX;
			break;
		case ARP_RX:
			if(ARPIsResolved(&gw, &dhcpServer.MACAddr))
			{
				s = OPEN_S;
				timeoutARP = 0;
			}
			else
			{
				timeoutARP++;
				if(timeoutARP >= ARP_TIMEOUT)
				{
					s = ARP_GW;
					timeoutARP = 0;
				}
			}
			break;
		case OPEN_S:
			tx = UDPOpen((UDP_PORT)67, &dhcpServer, (UDP_PORT)67);
			
			if(rx == INVALID_UDP_SOCKET)
			{
#ifdef DEBUG
				DisplayString(0,"RXCvTXSiRXS TXC ");
#endif
			}
			else
			{
#ifdef DEBUG
				DisplayString(0,"RXCvTXSvRXS TXC ");
#endif
				s = TX_S;
			}
			break;
		case TX_S:
			if(UDPIsPutReady(tx))
			{
				c = 0;
				for(i = offsetRelayIP; i < offsetRelayIP + 4; i++)
				{
					buffer[i] = gwPubIP[c];
					c++;
				}
				
				pTx = &buffer[0];
				while(UDPPut(*pTx) == TRUE && pTx != pRx)
					pTx++;
				
				UDPFlush();
				
				UDPClose(tx);
				
				s = LISTEN_S;
			}
			break;
		case LISTEN_S:
			pRx = &buffer[0];
			pTx = &buffer[0];
		
			rx = UDPOpen((UDP_PORT)67, NULL, 67);
			
			if(rx == INVALID_UDP_SOCKET)
			{
#ifdef DEBUG
				DisplayString(0,"RXCvTXSvRXSiTXC ");
#endif
			}
			else
			{
#ifdef DEBUG
				DisplayString(0,"RXCvTXSvRXSvTXC ");
#endif
				s = RX_S;
			}
			break;
		case RX_S:
			if(UDPIsGetReady(rx))
			{
				while(UDPGet(pRx) == TRUE)
					pRx++;
				
				UDPDiscard();
				
				s = OPEN_C;
				
				UDPClose(rx);
			}
			break;
		case OPEN_C:
			tx = UDPOpen((UDP_PORT)67, &broadcast, (UDP_PORT)68);
			
			if(rx == INVALID_UDP_SOCKET)
			{
#ifdef DEBUG
				DisplayString(0,"RXCvTXSvRXSvTXCi");
#endif
			}
			else
			{
#ifdef DEBUG
				DisplayString(0,"RXCvTXSvRXSvTXCv");
#endif
				s = TX_C;
			}
			break;
		case TX_C:
			if(UDPIsPutReady(tx))
			{
				//Dirty hack for NAT, this should be done in more places: in options from DHCP OFFER and DHCP REQUEST!
/*				c = 0;
				for(i = offsetClientIP; i < offsetClientIP + 4; i++)
				{
					buffer[i] = (myIP[c] & myNM[c]) | (buffer[i] & (~myNM[c]));
					c++;
				}
*/
				pTx = &buffer[0];
				while(UDPPut(*pTx) == TRUE && pTx != pRx)
					pTx++;
				
				UDPFlush();
				
				UDPClose(tx);
				
				s = LISTEN_C;
			}
			break;
	}
}

static void init()
{
	int i;

	OSCTUNE = 0x40;

        if(OSCCONbits.IDLEN)
		OSCCON = 0x82;
	else
		OSCCON = 0x02;

	TRISGbits.TRISG5 = 0;

	RCONbits.IPEN = 1;
        INTCONbits.GIEH = 1;
        INTCONbits.GIEL = 1;

        LCDInit();

        DelayMs(100);

        TickInit();

        InitAppConfig();

        StackInit();

        UDPInit();
        
        dhcpServer.IPAddr.Val = (DWORD)dhcpServerIP[0] | (DWORD)dhcpServerIP[1] << 8 | (DWORD)dhcpServerIP[2] << 16 | (DWORD)dhcpServerIP[3] << 24;
	
	gw.Val = (DWORD)gwIP[0] | (DWORD)gwIP[1] << 8 | (DWORD)gwIP[2] << 16 | (DWORD)gwIP[3] << 24;
	
	broadcast.IPAddr.Val = 0xFFFFFFFF;
	for(i = 0; i < 6; i++)
		broadcast.MACAddr.v[i] = 0xFF;

	// Client doesn't have an IP address yet
	client.IPAddr.Val = (DWORD)255 | (DWORD)255 << 8 | (DWORD)255 << 16 | (DWORD)255 << 24;
}

static void InitAppConfig()
{
	AppConfig.Flags.bIsDHCPEnabled = FALSE;
	AppConfig.Flags.bInConfigMode = TRUE;

	AppConfig.MyMACAddr.v[0] = 0;
	AppConfig.MyMACAddr.v[1] = 0x04;
	AppConfig.MyMACAddr.v[2] = 0xA3;
	AppConfig.MyMACAddr.v[3] = 0x01;
	AppConfig.MyMACAddr.v[4] = 0x02;
	AppConfig.MyMACAddr.v[5] = 0x03;

	AppConfig.MyIPAddr.Val = (DWORD)myIP[0] | (DWORD)myIP[1] << 8 | (DWORD)myIP[2] << 16 | (DWORD)myIP[3] << 24;
	AppConfig.DefaultIPAddr.Val = AppConfig.MyIPAddr.Val;
	AppConfig.MyMask.Val = (DWORD)myNM[0] | (DWORD)myNM[1] << 8 | (DWORD)myNM[2] << 16 | (DWORD)myNM[3] << 24;
	AppConfig.DefaultMask.Val = AppConfig.MyMask.Val;
	AppConfig.MyGateway.Val = (DWORD)gwIP[0] | (DWORD)gwIP[1] << 8 | (DWORD)gwIP[2] << 16 | (DWORD)gwIP[3] << 24;
	AppConfig.PrimaryDNSServer.Val = (DWORD)DNS1IP[0] | (DWORD)DNS1IP[1] << 8 | (DWORD)DNS1IP[2] << 16 | (DWORD)DNS1IP[3] << 24;
	AppConfig.SecondaryDNSServer.Val = (DWORD)DNS2IP[0] | (DWORD)DNS2IP[1] << 8 | (DWORD)DNS2IP[2] << 16 | (DWORD)DNS2IP[3] << 24;
}

void DisplayIPValue(DWORD IPdw)
{
	BYTE IPDigit[4];
	BYTE i;
	BYTE j;
	BYTE LCDPos=16;
	unsigned int IP_field, radix=10;

	for(i = 0; i < sizeof(IP_ADDR); i++)
	{
		IP_field =(WORD)(IPdw>>(i*8))&0xff;
		uitoa(IP_field, IPDigit, radix);

		for(j = 0; j < strlen((char*)IPDigit); j++)
		{
			LCDText[LCDPos++] = IPDigit[j];
		}
		if(i == sizeof(IP_ADDR)-1)
			break;
		LCDText[LCDPos++] = '.';

	}

	if(LCDPos < 32u)
		LCDText[LCDPos] = 0;

	LCDUpdate();
}

void DisplayString(BYTE pos, char* text)
{
	BYTE l = strlen(text);
	BYTE max = 32 - pos;
	strlcpy((char*)&LCDText[pos], text, (l < max) ? l : max);
	LCDUpdate();
}

size_t strlcpy(char *dst, const char *src, size_t siz)
{
	char       *d = dst;
	const char *s = src;
	size_t      n = siz;

	/* Copy as many bytes as will fit */
	if (n != 0)
	{
		while (--n != 0)
		{
			if (*d == '\0')
				break;
			else
				*d++ = *s++;
		}
	}

	/* Not enough room in dst, add NUL and traverse rest of src */
	if (n == 0)
	{
		if (siz != 0)
		*d = '\0';          /* NUL-terminate dst */
		while (*s++)
		;
	}

	return (s - src - 1);       /* count does not include NUL */
}

